/*
 * $Id: QECallsign.m 248 2008-09-08 15:31:38Z jon $
 *
 * Copyright Jon Gordon
 * Created by Jon Gordon on 12/28/07.
 *
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#import "QECallsign.h"

#import "Sleeper.h"


static NSPredicate *baseCallsignPredicate;
static NSPredicate *callsignPredicate;


@implementation QECallsign
+ (void)initialize
{
	static BOOL initialized = NO;
    
	if (!initialized) {
		initialized = YES;
		NSLog(@"initializing QECallsign");
        
		/**********************************************************************************************
		 *    This honkin'-long comment is followed immediately by the definition of a string that
		 * includes a regular expression that is meant to recognize any possible call sign.
		 * The expression itself is long and complex enough as to be indecipherable, unmaintainable,
		 * and undebuggable.  Thus, set forth below, in what is doubtless an abuse of Backus-Naur
		 * form, is the structure of a call sign, as the regex is supposed to recognize.
		 *
		 * <call> ::= <prefixed_call> | <possibly_suffixed_call>
		 *
		 * <prefixed_call> ::= <itu_prefix> <digit>? '/' <base_call>
		 *
		 * <possibly_suffixed_call> ::= <base_call> <optional_suffix>?
		 *
		 * <optional_suffix> ::= '/' <optional_suffix_body>
		 *
		 * <optional_suffix_body> ::= (<itu_prefix> <digit>?) | 'm' | 'mm' | 'a' | 'p' | <digit>
		 *
		 * <base_call> ::= <itu_prefix> <call_suffix>
		 *
		 * <call_suffix> ::= <character>{0,6} <letter>
		 *
		 * <character> ::= <letter> | <digit>
		 *
		 * <itu_prefix> ::= <itu_one_prefix> | <itu_two_prefix> | <itu_three_prefix>
		 *
		 * <itu_three_prefix> ::= <split_two_prefix> <letter>
		 *
		 * <split_two_prefix> ::= <prefix_split_digit> <prefix_split_letter>
		 *
		 * <prefix_split_letter> ::= 'd'
		 *
		 * <itu_two_prefix> ::= <letter_two_prefix> | <digit_two_prefix>
		 *
		 * <letter_two_prefix> ::= <prefix_first_letter> <prefix_character>
		 *
		 * <prefix_first_letter> ::= 'a'|'c'|'d'|'e'|'h'|'j'|'l'|'o'|'p'|'s'|'t'|'u'|'v'|'x'|'y'|'z'
		 *   # no prefix is allowed to begin with 'Q', to prevent confusion with Q-signals
		 *
		 * <digit_two_prefix> ::= (<prefix_split_digit> <prefix_nonsplit_letter>)|
		 *                        (<prefix_nonsplit_digit> <letter>)
		 *
		 * <prefix_nonsplit_letter> ::= 'a'|'b|'c'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
		 *                              'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'
		 *
		 * <itu_one_prefix> ::= 'b'|'f'|'g'|'i'|'k'|'m'|'n'|'r'|'w' <prefix_character>?
		 *
		 * <prefix_character> ::= <letter> | <prefix_digit>
		 *
		 * <prefix_digit> ::= <prefix_split_digit> | <prefix_nonsplit_digit>
		 *   # An ITU-assigned national call sign prefix may have at most one decimal digit,
		 *   # which may not be 0 or 1
		 *
		 * <prefix_split_digit> ::= '3'
		 *   # the only ITU prefixes that must be three characters long begin with
		 *   # '3', followed by 'd'
		 *
		 * <prefix_nonsplit_digit> ::= '2'|'4'|'5'|'6'|'7'|'8'|'9'
		 */
        
		NSString *prefix_nonsplit_digit = @"[24-9]";
		NSString *prefix_split_digit    = @"3";
		//        NSString *prefix_digit          = @"[2-9]";
		NSString *prefix_character      = @"[a-z2-9]";
		NSString *itu_one_prefix        = @"[bfgikmnruw][a-z2-9]?";
		NSString *prefix_nonsplit_letter = @"[a-ce-z]";
		NSString *digit_two_prefix       = [NSString stringWithFormat:@"(?:%@%@)|(?:%@[a-z])",
											prefix_split_digit,
											prefix_nonsplit_letter,
											prefix_nonsplit_digit];
		NSString *prefix_first_letter    = @"[ac-ehjlopstvx-z]";
		NSString *letter_two_prefix      = [NSString stringWithFormat:@"%@%@",
											prefix_first_letter,
											prefix_character];
		NSString *itu_two_prefix         = [NSString stringWithFormat:@"(?:%@)|(?:%@)",
											letter_two_prefix,
											digit_two_prefix];
		NSString *prefix_split_letter    = @"d";
		NSString *split_two_prefix       = [NSString stringWithFormat:@"%@%@",
											prefix_split_digit,
											prefix_split_letter];
		NSString *itu_three_prefix       = [NSString stringWithFormat:@"%@[a-z]",
											split_two_prefix];
		NSString *itu_prefix             = [NSString stringWithFormat:@"(?:%@)|(?:%@)|(?:%@)",
											itu_one_prefix,
											itu_two_prefix,
											itu_three_prefix];
		NSString *call_prefix            = [NSString stringWithFormat:@"(?:%@)[0-9]?",
											itu_prefix];
		NSString *call_suffix            = @"(?:[a-z0-9]{0,5})[a-z]";
		NSString *base_call              = [NSString stringWithFormat:@"(?:%@)%@",
											call_prefix,
											call_suffix];
		NSString *optional_suffix        = [NSString stringWithFormat:@"\\/(?:(?:%@)|(?:mm?)|[ap0-9])",
											call_prefix];
		NSString *possibly_suffixed_call = [NSString stringWithFormat:@"(?:%@)(?:%@)?",
											base_call,
											optional_suffix];
		NSString *prefixed_call          = [NSString stringWithFormat:@"(?:%@)\\/(?:%@)(?:\\/m)?",
											call_prefix,
											base_call];
		NSString *call                   = [NSString stringWithFormat:@"(?:%@)|(?:%@)",
											prefixed_call,
											possibly_suffixed_call];
        
		NSLog(@"      digit_two_prefix: %@", digit_two_prefix);
		NSLog(@"     letter_two_prefix: %@", letter_two_prefix);
		NSLog(@"        itu_two_prefix: %@", itu_two_prefix);
		NSLog(@"      split_two_prefix: %@", split_two_prefix);
		NSLog(@"      itu_three_prefix: %@", itu_three_prefix);
		NSLog(@"            itu_prefix: %@", itu_prefix);
		NSLog(@"           call_prefix: %@", call_prefix);
		NSLog(@"           call_suffix: %@", call_suffix);
		NSLog(@"             base_call: %@", base_call);
		NSLog(@"       optional_suffix: %@", optional_suffix);
		NSLog(@"possibly_suffixed_call: %@", possibly_suffixed_call);
		NSLog(@"         prefixed_call: %@", prefixed_call);
		NSLog(@"                  call: %@", call);
        
		NSString *baseCallsignPattern = [[NSString stringWithFormat:@"^(?i)%@$", base_call] retain];
		NSString *callsignPattern = [[NSString stringWithFormat:@"^(?i)%@$", call] retain];
        
        baseCallsignPredicate = [[NSPredicate predicateWithFormat:@"self matches %@", baseCallsignPattern] retain];
        callsignPredicate = [[NSPredicate predicateWithFormat:@"self matches %@", callsignPattern] retain];
	} // if
} // initialize


+ (BOOL)isValidCallsign:(NSString *)callsign
{
	if (nil == callsign)
		return NO;
	if (0 == [callsign length])
		return NO;
    
    return [callsignPredicate evaluateWithObject:callsign];
} // isValidCallsign:


+ (BOOL)isValidCallsign:(id *)ioValue error:(NSError **)outError
{
    BOOL result = NO;
    
    if (nil == *ioValue) {
        result = YES;
    } else if ([QECallsign isValidCallsign:(*ioValue)]) {
        result = YES;
        NSString *capitalizedValue = [*ioValue uppercaseString];
        *ioValue = capitalizedValue;
    } else if (nil != outError) {
        int errorCode = QECallsignInvalidString;
        NSString *description = @"Invalid callsign entered.";
        NSString *reason = [NSString stringWithFormat:@"\"%@\" appears not to be a valid callsign.", *ioValue];
        NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
        NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, nil];
        NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
        *outError = [[[NSError alloc] initWithDomain:QESleeperErrorDomain code:errorCode userInfo:eDict] autorelease];
    } // else if
    
    return result;
} // isValidBaseCallsign:error:


+ (BOOL)isValidBaseCallsign:(NSString *)callsign
{
	if (nil == callsign)
		return NO;
	if (0 == [callsign length])
		return NO;
	
    return [baseCallsignPredicate evaluateWithObject:callsign];
} // isValidBaseCallsign


+ (BOOL)isValidBaseCallsign:(id *)ioValue error:(NSError **)outError
{
    BOOL result = NO;
    
    if (nil == *ioValue) {
        result = YES;
    } else if ([QECallsign isValidBaseCallsign:(*ioValue)]) {
        result = YES;
        NSString *capitalizedValue = [*ioValue uppercaseString];
        *ioValue = capitalizedValue;
    } else if (nil != outError) {
        int errorCode = QECallsignInvalidBaseString;
        NSString *description = [NSString stringWithFormat:@"\"%@\" appears not to be a valid callsign.", *ioValue];
        NSArray *objArray = [NSArray arrayWithObjects:description, nil];
        NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, nil];
        NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
        *outError = [[[NSError alloc] initWithDomain:QESleeperErrorDomain code:errorCode userInfo:eDict] autorelease];
    } // else if
    
    return result;
} // isValidBaseCallsign:error:
@end
